<?php

declare(strict_types=1);

namespace core;

class Logger
{
    private string $logDir;
    private const DEFAULT_PERMISSIONS = 0755;
    private const MAX_FILE_SIZE       = 1048576;  // 1MB

    public function __construct(?string $logDir = null)
    {
        $this->logDir = $logDir ?? __DIR__ . '/../runtime/logs';
        $this->logDir = rtrim($this->logDir, '/');

        // 如果日志目录不存在，尝试创建
        if (!is_dir($this->logDir) && !mkdir($this->logDir, self::DEFAULT_PERMISSIONS, true)) {
            throw new \RuntimeException(sprintf('无法创建日志目录: %s', $this->logDir));
        }

        // 验证目录是否可写
        if (!is_writable($this->logDir)) {
            throw new \RuntimeException(sprintf('日志目录不可写: %s', $this->logDir));
        }
    }

    /**
     * 写入日志
     *
     * @param string $message 日志内容
     * @param string $level 日志级别，如 INFO, ERROR, DEBUG
     * @return bool 成功返回 true，失败抛出异常或返回 false（可配置）
     */
    public function log(string $message, string $level = 'INFO'): void
    {
        $level = strtoupper($level);
        $allowedLevels = ['INFO', 'ERROR', 'DEBUG', 'WARNING', 'CRITICAL'];
        if (!in_array($level, $allowedLevels, true)) {
            $level = 'INFO'; // 默认级别
        }

        $date = date('Y-m-d');
        $filePath = $this->logDir . '/' . $date . '.log';

        // 检查日志轮转
        clearstatcache(true, $filePath); // 避免缓存导致 filesize 不准确
        if (file_exists($filePath) && filesize($filePath) >= self::MAX_FILE_SIZE) {
            $this->rotateLog($filePath);
        }

        $time = date('H:i:s');
        // 过滤敏感信息和特殊字符
        $sanitizedMessage = $this->sanitizeMessage($message);
        $logMessage = "[$time] [$level] {$sanitizedMessage}" . PHP_EOL;

        // 使用原子写入，避免并发冲突
        $fp = @fopen($filePath, 'a');
        if (!$fp) {
            throw new \RuntimeException(sprintf('无法打开日志文件: %s', $filePath));
        }

        try {
            if (!flock($fp, LOCK_EX)) {
                throw new \RuntimeException(sprintf('无法锁定日志文件: %s', $filePath));
            }
            fwrite($fp, $logMessage);
        } finally {
            fclose($fp);
        }
    }

    /**
     * 日志轮转：防止单个文件过大
     */
    private function rotateLog(string $filePath): void
    {
        $backupPath = $filePath . '.' . date('Ymd_His') . '.bak';
        if (!@rename($filePath, $backupPath)) {
            throw new \RuntimeException(sprintf('无法轮转日志文件: %s', $filePath));
        }
    }

    /**
     * 过滤敏感信息和特殊字符
     */
    private function sanitizeMessage(string $message): string
    {
        // 移除控制字符（除了换行和回车）
        $message = preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $message);

        // 这里可以添加更多敏感信息过滤逻辑
        // 例如：信用卡号、密码等

        return $message;
    }

    public function info(string $message): void
    {
        $this->log($message, 'INFO');
    }

    public function error(string $message): void
    {
        $this->log($message, 'ERROR');
    }

    public function debug(string $message): void
    {
        $this->log($message, 'DEBUG');
    }

    public function warning(string $message): void
    {
        $this->log($message, 'WARNING');
    }
}
